// ========================================================
// Des：
// Creator：DD 
// Time：2020-11-23 11:55:33
// Version：1.0
// ========================================================

/*
 * Advanced C# messenger by Ilya Suzdalnitski. V1.0
 * 
 * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
 * 
 * Features:
    * Prevents a MissingReferenceException because of a reference to a destroyed message handler.
    * Option to log all messages
    * Extensive error detection, preventing silent bugs
 * 
 * Usage examples:
    1. Messenger.AddListener<GameObject>("prop collected", PropCollected);
       Messenger.Broadcast<GameObject>("prop collected", prop);
    2. Messenger.AddListener<float>("speed changed", SpeedChanged);
       Messenger.Broadcast<float>("speed changed", 0.5f);
 * 
 * Messenger cleans up its evenTable automatically upon loading of a new level.
 * 
 * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(NotifyMessageType)
 * 
 */

//#define LOG_ALL_MESSAGES
//#define LOG_ADD_LISTENER
//#define LOG_BROADCAST_MESSAGE
//#define REQUIRE_LISTENER

using System;
using System.Collections.Generic;
using UnityEngine;

public static class Messenger
{
    #region Internal variables
    //Ensures that the MessengerHelper will be created automatically upon start of the game.
    private static MessengerHelper s_MessengerHelper = new GameObject("MessengerHelper").AddComponent<MessengerHelper>();

    public static Dictionary<Enum, Delegate> eventTable = new Dictionary<Enum, Delegate>();

    //Message handlers that should never be removed, regardless of calling Cleanup
    public static List<Enum> permanentMessages = new List<Enum>();
    #endregion

    #region Helper methods
    //Marks a certain message as permanent.
    public static void MarkAsPermanent(Enum eventType)
    {
#if LOG_ALL_MESSAGES
	        Debug.Log("Messenger MarkAsPermanent \t\"" + eventType + "\"");
#endif
        permanentMessages.Add(eventType);
    }

    public static void Cleanup()
    {
#if LOG_ALL_MESSAGES
	        Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
#endif

        List<Enum> messagesToRemove = new List<Enum>();

        foreach (KeyValuePair<Enum, Delegate> pair in eventTable)
        {
            bool wasFound = false;

            foreach (Enum message in permanentMessages)
            {
                if (pair.Key == message)
                {
                    wasFound = true;
                    break;
                }
            }

            if (!wasFound)
                messagesToRemove.Add(pair.Key);
        }

        foreach (Enum message in messagesToRemove)
        {
            eventTable.Remove(message);
        }
    }

    public static void PrintEventTable()
    {
        Debug.Log("\t\t\t=== MESSENGER PrintEventTable ===");

        foreach (KeyValuePair<Enum, Delegate> pair in eventTable)
        {
            Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value);
        }

        Debug.Log("\n");
    }
    #endregion

    #region Message logging and exception throwing

    public static void OnListenerAdding(Enum eventType, Delegate listenerBeingAdded)
    {
#if LOG_ALL_MESSAGES || LOG_ADD_LISTENER
	        Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType + "\"\t{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
#endif

        if (!eventTable.ContainsKey(eventType))
        {
            eventTable.Add(eventType, null);
        }

        Delegate d = eventTable[eventType];
        if (d != null && d.GetType() != listenerBeingAdded.GetType())
        {
            throw new ListenerException(
                $"Attempting to add listener with inconsistent signature for event type {eventType}. Current listeners have type {d.GetType().Name} and listener being added has type {listenerBeingAdded.GetType().Name}");
        }
    }

    public static void OnListenerRemoving(Enum eventType, Delegate listenerBeingRemoved)
    {
#if LOG_ALL_MESSAGES
	        Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType + "\"\t{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
#endif

        if (eventTable.ContainsKey(eventType))
        {
            Delegate d = eventTable[eventType];

            if (d == null)
            {
                throw new ListenerException(
                    $"Attempting to remove listener with for event type \"{eventType}\" but current listener is null.");
            }
            else if (d.GetType() != listenerBeingRemoved.GetType())
            {
                throw new ListenerException(
                    $"Attempting to remove listener with inconsistent signature for event type {eventType}. Current listeners have type {d.GetType().Name} and listener being removed has type {listenerBeingRemoved.GetType().Name}");
            }
        }
        else
        {
            throw new ListenerException(
                $"Attempting to remove listener for type \"{eventType}\" but Messenger doesn't know about this event type.");
        }
    }

    public static void OnListenerRemoved(Enum eventType)
    {
        if (eventTable[eventType] == null)
        {
            eventTable.Remove(eventType);
        }
    }

    public static void OnBroadcasting(Enum eventType)
    {
#if REQUIRE_LISTENER
            if (!eventTable.ContainsKey(eventType))
            {
                throw new BroadcastException(
                    $"Broadcasting message \"{eventType}\" but no listener found. Try marking the message with Messenger.MarkAsPermanent.");
            }
#endif
    }

    public static BroadcastException CreateBroadcastSignatureException(Enum eventType)
    {
        return new BroadcastException(
            $"Broadcasting message \"{eventType}\" but listeners have a different signature than the broadcaster.");
    }

    public class BroadcastException : Exception
    {
        public BroadcastException(string msg)
            : base(msg)
        {
        }
    }

    public class ListenerException : Exception
    {
        public ListenerException(string msg)
            : base(msg)
        {
        }
    }
    #endregion

    #region AddListener
    //No parameters
    public static void AddListener(Enum eventType, CallBack handler)
    {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (CallBack)eventTable[eventType] + handler;
    }

    //Single parameter
    public static void AddListener<T>(Enum eventType, CallBack<T> handler)
    {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (CallBack<T>)eventTable[eventType] + handler;
    }

    //Two parameters
    public static void AddListener<T, U>(Enum eventType, CallBack<T, U> handler)
    {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (CallBack<T, U>)eventTable[eventType] + handler;
    }

    //Three parameters
    public static void AddListener<T, U, V>(Enum eventType, CallBack<T, U, V> handler)
    {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (CallBack<T, U, V>)eventTable[eventType] + handler;
    }

    public static void AddListener<T, U, V, N>(Enum eventType, CallBack<T, U, V, N> handler)
    {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (CallBack<T, U, V, N>)eventTable[eventType] + handler;
    }
    #endregion

    #region RemoveListener
    //No parameters
    public static void RemoveListener(Enum eventType, CallBack handler)
    {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (CallBack)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }

    //Single parameter
    public static void RemoveListener<T>(Enum eventType, CallBack<T> handler)
    {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (CallBack<T>)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }

    //Two parameters
    public static void RemoveListener<T, U>(Enum eventType, CallBack<T, U> handler)
    {
        OnListenerRemoving(eventType, handler);
        // ReSharper disable DelegateSubtraction
        eventTable[eventType] = (CallBack<T, U>)eventTable[eventType] - handler;
        // ReSharper restore DelegateSubtraction
        OnListenerRemoved(eventType);
    }

    //Three parameters
    public static void RemoveListener<T, U, V>(Enum eventType, CallBack<T, U, V> handler)
    {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (CallBack<T, U, V>)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    #endregion

    #region Broadcast
    //No parameters
    public static void Broadcast(Enum eventType)
    {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
	        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToEventType("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        if (eventTable.TryGetValue(eventType, out Delegate d))
        {
            if (d is CallBack callback)
            {
                callback?.Invoke();
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Single parameter
    public static void Broadcast<T>(Enum eventType, T arg1)
    {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
	        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToEventType("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        if (eventTable.TryGetValue(eventType, out Delegate d))
        {
            if (d is CallBack<T> callback)
            {
                callback?.Invoke(arg1);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Two parameters
    public static void Broadcast<T, U>(Enum eventType, T arg1, U arg2)
    {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
	        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToEventType("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        if (eventTable.TryGetValue(eventType, out Delegate d))
        {
            if (d is CallBack<T, U> callback)
            {
                callback?.Invoke(arg1, arg2);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Three parameters
    public static void Broadcast<T, U, V>(Enum eventType, T arg1, U arg2, V arg3)
    {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
	        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToEventType("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        if (eventTable.TryGetValue(eventType, out Delegate d))
        {
            if (d is CallBack<T, U, V> callback)
            {
                callback?.Invoke(arg1, arg2, arg3);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    public static void Broadcast<T, U, V, N>(Enum eventType, T arg1, U arg2, V arg3, N arg4)
    {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
	        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToEventType("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        if (eventTable.TryGetValue(eventType, out Delegate d))
        {
            if (d is CallBack<T, U, V, N> callback)
            {
                callback?.Invoke(arg1, arg2, arg3, arg4);
            }
            else
            {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    #endregion
}

//This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level.
public sealed class MessengerHelper : MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }

    //Clean up eventTable every time a new level loads.
    public void OnLevelWasLoaded(int unused)
    {
        Messenger.Cleanup();
    }
}

public delegate void CallBack();
public delegate void CallBack<T>(T t);
public delegate void CallBack<T, T1>(T t, T1 t1);
public delegate void CallBack<T, T1, T2>(T t, T1 t1, T2 t2);
public delegate void CallBack<T, T1, T2, T3>(T t, T1 t1, T2 t2, T3 t3);

public delegate R CallBackR<R>();
public delegate R CallBackR<R, T>(T t);
public delegate R CallBackR<R, T, T1>(T t, T1 t1);
public delegate R CallBackR<R, T, T1, T2>(T t, T1 t1, T2 t2);
public delegate R CallBackR<R, T, T1, T2, T3>(T t, T1 t1, T2 t2, T3 t3);

public enum EnumLogicMsg
{
    /// <summary>
    /// 显示提示框
    /// </summary>
    ShowTips,
    /// <summary>
    /// 显示确认框
    /// </summary>
    ShowConfirmBox,
    /// <summary>
    /// 显示加载提示
    /// </summary>
    ShowLoadingTips,
    /// <summary>
    /// 显示加载提示内容
    /// </summary>
    ShowLoadingTipWithContens,
    /// <summary>
    /// 开始定位图片追踪定位
    /// </summary>
    StartLocateImageTrack,
    StartDynamicLocateImageTrack,
    /// <summary>
    /// 停止定位图片追踪定位
    /// </summary>
    StopLocateImageTrack,
    StopDynamicLocateImageTrack,
    /// <summary>
    /// 结束定位图片追踪定位
    /// </summary>
    EndLocateImageTrack,
    EndDynamicLocateImageTrack,
    /// <summary>
    /// 开始同步坐标系图片追踪定位
    /// </summary>
    StartSyncCoordinateImageTrack,
    /// <summary>
    /// 开始截图
    /// </summary>
    StartScreenShot,
    /// <summary>
    /// 结束截图
    /// </summary>
    EndScreenShot,
    /// <summary>
    /// 重置模型操作状态
    /// </summary>
    ResetStatus,
    /// <summary>
    /// 设置定位锚点
    /// </summary>
    SetLocateWorldAnchor,
    BackLocateWorldAnchor,
    /// <summary>
    /// 加载模型
    /// </summary>
    LoadingModel,
    /// <summary>
    /// 开始登录
    /// </summary>
    StartLogin,
    /// <summary>
    /// 登录结果
    /// </summary>
    LoginResult,
    /// <summary>
    /// 会议服务器
    /// </summary>
    ServerDisconnect,
    /// <summary>
    /// 开始同步坐标系
    /// </summary>
    StartSyncCoordinateSystem,
    /// <summary>
    /// 结束同步坐标系
    /// </summary>
    EndSyncCoordinateSystem,
    /// <summary>
    /// 接收到图片文件                                                                     
    /// </summary>
    ReceivedPic,
    /// <summary>
    /// 展示本地图片文件                                                                   
    /// </summary>
    ShowLocalPic,
    /// <summary>
    /// 展示本地视频文件                                                                   
    /// </summary>
    ShowLocalVideo,
    /// <summary>
    /// 查看当前会议的历史图片
    /// </summary>
    CheckHistoryPic,
    /// <summary>
    /// Vuforia模型识别定位
    /// </summary>
    StartLocateModelTrack,
    StopLocateModelTrack,
    StartAlwaysLocateModelTrack,
    StopAlwaysLocateModelTrack,

    /// <summary>
    /// 网络同步穿刺
    /// </summary>
   SyncPuncturePath,
}

public enum EnumLogMsg
{
    Log,
}

public enum EnumMultiMeetingMsg
{
    HANG,
    ACCEPT,
    SwitchControl,
    AddNewRoomMember,
    FreshRoomMemberList,
    OpenRoomUI,
    CloseRoomUI,
    RemoveRoomMember,
    JoinRoomRequest,
}

public enum EnumUIMsg
{
    SetUserPanelActive,

    SetModelOpePanelActive,
    SetLoginPanelActive,
    SetClassicOrCloudPanelActive,
    SetDepartmentsSelectPanelActive,
    SetImplantTeethPanelActive,
    SetRoomListPanelActive,
    SetRoomWaitPanelActive,
    SetRemoteMeetingPanelActive,
    SetPreMeetingPanelActive,
    SetMeetingWaitPanelActive,
    SetInMeetingPanelActive,
    SetBanMoGuidePanelActive,
    SetQRcodePanelActive,

    SetClassicOrCloudPanelLocalPos,
    SetDepartmentsSelectPanelLocalPos,
    SetLoginPanelLocalPos,
    SetModelOpePanelLocalPos,

    DisableAllOpeBtnLight,
    DisableAllStatusOpeBtnLight,

    RefreshPanelBtn,

    RefreshPictureBtn,
    RefreshVideoBtn,
}

public enum EnumRoomMsg
{
    CreateRoomCRES,
    CreateRoomSRES,

    GetRoomListCRES,
    GetRoomListSRES,

    UpdateRoomInfoCRES,
    UpdateRoomInfoSRES,

    JoinRoomInfoCRES,
    JoinRoomInfoSRES,

    LeaveRoomCRES,
    LeaveRoomSRES,

    StartMeetingCRES,
    StartMeetingSRES,

    /// <summary>
    /// 解散会议
    /// </summary>
    DissolveMeetingCRES,
    DissolveMeetingSRES,

    /// <summary>
    /// 同步命令
    /// </summary>
    SyncCommand,
    /// <summary>
    /// 同步transform
    /// </summary>
    SyncTransform,
    /// <summary>
    /// 给出权限
    /// </summary>
    SwitchControl,
    /// <summary>
    /// 改变会议模型
    /// </summary>
    ChangeMeetingModel,
}

public enum EnumModelOpeMsg
{
    /// <summary>
    /// 打开模型
    /// </summary>
    ModelOpen,
    /// <summary>
    /// 关闭模型
    /// </summary>
    ModelClose,
    /// <summary>
    /// 复位
    /// </summary>
    ModelReset,
    /// <summary>
    /// 单选
    /// </summary>
    ModelSingleSelect,
    /// <summary>
    /// 隐藏 
    /// </summary>
    ModelHide,
    /// <summary>
    /// 显示
    /// </summary>
    ModelShow,
    /// <summary>
    /// 自动旋转
    /// </summary>
    ModelAutoRotate,
    /// <summary>
    /// 自动分散
    /// </summary>
    ModelDisperse,
    /// <summary>
    /// 病灶扩散
    /// </summary>
    NidusDiffuse,
    /// <summary>
    /// 病灶缩小
    /// </summary>
    NidusShrink,
    /// <summary>
    /// 病灶投影
    /// </summary>
    NidusProjection,
    /// <summary>
    /// 模型对比，打开多个模型
    /// </summary>
    OpenMultipleModels,
    /// <summary>
    /// 手动旋转
    /// </summary>
    ModelHandRotate,
}

public enum EnumExtraOpeMsg
{
    /// <summary>
    /// 固定引导线
    /// </summary>
    FixGuideLine,
    /// <summary>
    /// 固定模型
    /// </summary>
    FixModel,
    /// <summary>
    /// 开始演示
    /// </summary>
    StartGuide,
    /// <summary>
    /// 手势触发（握拳到张开）
    /// </summary>
    GestureFistToOpen,
}